﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Web.Services.Description;
using System.Xml;
using System.Reflection;
using System.IO;
using WSDLParser;
using System.Xml.Schema;
using AbstraX.ServerInterfaces;
using AbstraX.Bindings;
using AbstraX.TypeMappings;
using CodeGenerationPipeline;
using CodeGenerationPipeline.Contracts;
using AbstraX.Contracts;

namespace WSDLGenerator
{
    public class PackageGenerator : WSDLGeneratorBase
    {
        public static IPackage GeneratePackage(Dictionary<string, IElementBuild> builds)
        {
            return new Package(builds);
        }

        public static IPackage GeneratePackage(ICodeGenerationPackage incomingPackage)
        {
            //return Generator.GeneratePackage(incomingPackage)   // returns IPackage
            //    .AppyServiceType(typeof(IServiceProvider))      // returns IServiceType
            //        .AddGenericArgu()       
            //        .AddParm()
            //        .AddParm()
            //            .Support                                // returns IDataContextSupporter
            //                .MapAll(st, odco => st.GetMethodInfo.Where(m => m.Name == odco.Name))  // st = ServiceType, odco = OptionalDataContextOperation, returns IDataContextSupporterMapper
            //                    .When(odco => odco.Insert)
            //                        .Create(typeof(IServiceProvider))
            //                            .MapMember
            //                            .MapParm
            //                .Where(st, ddco => st.GetMethodInfo.Where(m => m.Name == ddco.Name))  // st = ServiceType, ddco = DefaultDataContextOperation
            //                    .When(odco => odco.GetAll)
            //                        .Return(typeof(IServiceProvider))
            //                            .MapMember
            //            .Requirements // returns IRequirements
            //                .Add(
            //                .Add(

            return null;
        }

        public bool MapsTo(IElement childElement, PropertyInfo property)
        {
            return false;
        }

        public void GenerateFromElement(IElement element)
        {
            // load an "empty" WSDL file (no types, messages, operations, ...) 
            // to start from and add operations by code

            var resourceStream = Assembly.GetAssembly(this.GetType()).GetManifestResourceStream(@"WSDLGenerator.Empty.wsdl");
            var wsdl = ServiceDescription.Read(resourceStream);

            var binding = new Binding();
            var service = new Service();
            var portType = new PortType();

            wsdl.Bindings.Add(binding);
            wsdl.Services.Add(service);
            wsdl.PortTypes.Add(portType);

            wsdl.Types = new Types();

            //var stream = File.OpenRead(@"..\..\..\WSDLGenerator\ArchitectureModel.xsd");
            //var schema = XmlSchema.Read(stream, null);

            var schema = new XmlSchema();

            schema.Namespaces.Add("ser", "http://schemas.microsoft.com/2003/10/Serialization/");
            schema.Namespaces.Add("tns", "http://schemas.datacontract.org/2004/07/SolutionFramework.Entities");
            schema.Namespaces.Add("xs", "http://www.w3.org/2001/XMLSchema");
            schema.ElementFormDefault = XmlSchemaForm.Qualified;
            schema.TargetNamespace = "http://schemas.datacontract.org/2004/07/SolutionFramework.Entities";

            // add IElement 

            var complexType = new XmlSchemaComplexType()
            {
                Name = element.Name,
            };

            schema.Items.Add(complexType);

            // add ChildElements

            if (element.ChildElements.Count() > 0)
            {
                var sequence = new XmlSchemaSequence();

                complexType.Particle = sequence;

                foreach (IElement childElement in element.ChildElements)
                {
                    IElement childElementType;

                    if (childElement.IsContainer)
                    {
                        childElementType = childElement.ChildElements.First();
                    }
                    else
                    {
                        childElementType = childElement;
                    }

                    // add IElement

                    var complexTypeChild = new XmlSchemaComplexType()
                    {
                        Name = childElementType.Name,
                    };

                    if (childElementType.Attributes.Count() > 0)
                    {
                        var sequenceChild = new XmlSchemaSequence();

                        complexTypeChild.Particle = sequenceChild;

                        foreach (var attribute in childElementType.Attributes)
                        {
                            var schemaAttributeElementChild = new XmlSchemaElement();
                            var typeChild = Type.GetType(attribute.DataType.FullyQualifiedName);
                            var xmlTypeChild = typeChild.GetXMLType();
                            var xmlSchemaType = XmlSchemaType.GetBuiltInSimpleType(xmlTypeChild);

                            xmlSchemaType.Name = "xs:" + Enum.GetName(typeof(XmlTypeCode), xmlTypeChild);

                            schemaAttributeElementChild.Name = attribute.Name;
                            schemaAttributeElementChild.MinOccurs = 0;
                            schemaAttributeElementChild.SchemaTypeName = new XmlQualifiedName("xs:" + Enum.GetName(typeof(XmlTypeCode), xmlTypeChild).ToLower());

                            schemaAttributeElementChild.GetType().InvokeMember("SetElementType", BindingFlags.InvokeMethod | BindingFlags.NonPublic | BindingFlags.Instance, null, schemaAttributeElementChild, new object[] { xmlSchemaType });

                            sequenceChild.Items.Add(schemaAttributeElementChild);
                        }
                    }

                    schema.Items.Add(complexTypeChild);

                    var schemaChildElement = new XmlSchemaElement();

                    schemaChildElement.Name = childElement.Name;
                    schemaChildElement.MinOccurs = 0;
                    schemaChildElement.SchemaTypeName = new XmlQualifiedName("tns:" + complexTypeChild.Name);

                    schemaChildElement.GetType().InvokeMember("SetElementType", BindingFlags.InvokeMethod | BindingFlags.NonPublic | BindingFlags.Instance, null, schemaChildElement, new object[] { complexTypeChild });

                    sequence.Items.Add(schemaChildElement);
                }
            }

            // add Attributes

            if (element.Attributes.Count() > 0)
            {
                XmlSchemaSequence sequence;

                if (complexType.Particle == null)
                {
                    sequence = new XmlSchemaSequence();
                    complexType.Particle = sequence;
                }
                else
                {
                    sequence = (XmlSchemaSequence) complexType.Particle;
                }

                foreach (var attribute in element.Attributes)
                {
                    var schemaAttributeElement = new XmlSchemaElement();
                    var type = Type.GetType(attribute.DataType.FullyQualifiedName);
                    var xmlType = type.GetXMLType();
                    var xmlSchemaType = XmlSchemaType.GetBuiltInSimpleType(xmlType);

                    xmlSchemaType.Name = "xs:" + Enum.GetName(typeof(XmlTypeCode), xmlType);

                    schemaAttributeElement.Name = attribute.Name;
                    schemaAttributeElement.MinOccurs = 0;
                    schemaAttributeElement.SchemaTypeName = new XmlQualifiedName("xs:" + Enum.GetName(typeof(XmlTypeCode), xmlType).ToLower());

                    schemaAttributeElement.GetType().InvokeMember("SetElementType", BindingFlags.InvokeMethod | BindingFlags.NonPublic | BindingFlags.Instance, null, schemaAttributeElement, new object[] { xmlSchemaType });

                    sequence.Items.Add(schemaAttributeElement);
                }
            }

            wsdl.Types.Schemas.Add(schema);

            var outputStream = new MemoryStream();

            schema.Write(outputStream);

            var bytes = new byte[outputStream.Length];

            outputStream.Seek(0, SeekOrigin.Begin);
            outputStream.Read(bytes, 0, (int) outputStream.Length);

            var outputString = ASCIIEncoding.ASCII.GetString(bytes);

            // add operation binding

            var myOperationBinding = new OperationBinding();

            myOperationBinding.Name = "Add";

            Soap12OperationBinding myOperation = new Soap12OperationBinding();

            myOperation.Style = SoapBindingStyle.Document;
            myOperation.SoapAction = "http://localhost/remote/Add";
            myOperationBinding.Extensions.Add(myOperation);

            // add input binding

            InputBinding myInput = new InputBinding();
            Soap12BodyBinding soap12BodyInputBinding = new Soap12BodyBinding();

            soap12BodyInputBinding.Use = SoapBindingUse.Literal;

            myInput.Extensions.Add(soap12BodyInputBinding);
            myOperationBinding.Input = myInput;

            // add output binding

            OutputBinding myOutput = new OutputBinding();
            Soap12BodyBinding soap12BodyOutputBinding = new Soap12BodyBinding();

            soap12BodyOutputBinding.Use = SoapBindingUse.Literal;
            myOutput.Extensions.Add(soap12BodyOutputBinding);

            myOperationBinding.Output = myOutput;

            wsdl.Bindings[0].Operations.Add(myOperationBinding);

            // 1. ==> add operation to port type

            Operation operation = new Operation();

            operation.Name = "Add";

            OperationMessage outputMessage = (OperationMessage)new OperationOutput();

            outputMessage.Message = new XmlQualifiedName("tns:AddOutput");

            OperationInput inputMessage = new OperationInput();

            inputMessage.Message = new XmlQualifiedName("tns:AddInput");

            operation.Messages.Add(inputMessage);
            operation.Messages.Add(outputMessage);
            wsdl.PortTypes[0].Operations.Add(operation);

            // add first message

            Message message1 = new Message();

            message1.Name = "AddInput";

            MessagePart messagePart1 = new MessagePart();

            messagePart1.Name = "parameter1";

            messagePart1.Type = new XmlQualifiedName("s:int");

            MessagePart messagePart2 = new MessagePart();

            messagePart2.Name = "parameter2";

            messagePart2.Type = new XmlQualifiedName("s:int");

            message1.Parts.Add(messagePart1);
            message1.Parts.Add(messagePart2);

            // add second message

            Message message2 = new Message();

            message2.Name = "AddOutput";

            // add third message

            MessagePart messagePart3 = new MessagePart();

            messagePart3.Name = "result";
            messagePart3.Type = new XmlQualifiedName("s:int");
            message2.Parts.Add(messagePart3);

            // 2. ==> add messages

            wsdl.Messages.Add(message1);
            wsdl.Messages.Add(message2);

            // write the service description into a WSDL file.

            var outputStream2 = new MemoryStream();

            wsdl.Write(outputStream2);

            var bytes2 = new byte[outputStream2.Length];

            outputStream2.Seek(0, SeekOrigin.Begin);
            outputStream2.Read(bytes2, 0, (int) outputStream2.Length);

            var outputString2 = ASCIIEncoding.ASCII.GetString(bytes2);

            outputStream2.Seek(0, SeekOrigin.Begin);

            //var parser = new Parser(outputStream);
            
            Console.WriteLine("WSDL file named empty_add.wsdl created successfully.");
        }
    }
}
